package cn.stimd.spring.beans;

import java.beans.PropertyChangeEvent;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by Stimd
 * 项目地址：https://gitee.com/stimd/spring-wheel
 * 公众号：Java编程探微
 */
public abstract class AbstractPropertyAccessor extends TypeConverterSupport implements ConfigurablePropertyAccessor{
    //目标对象
    Object wrappedObject;

    public AbstractPropertyAccessor() {}

    public AbstractPropertyAccessor(Object wrappedObject) {
        this.wrappedObject = wrappedObject;
    }

    public AbstractPropertyAccessor(Object wrappedObject, AbstractPropertyAccessor parent) {
        this.wrappedObject = wrappedObject;
        setConversionService(parent.getConversionService());
    }


    /**
     * 获取获取属性（包括嵌套属性）值的功能
     */
    @Override
    public Object getPropertyValue(String propertyName) throws BeansException {
        //获取最深层次的属性，如果propertyName是a.b.c，那么nestPropertyName就是c
        String nestPropertyName = propertyName.substring(propertyName.lastIndexOf('.') + 1);
        return getPropertyAccessorForPropertyPath(propertyName).getPropertyValueInternal(nestPropertyName);
    }


    private Object getPropertyValueInternal(String propertyName){
        PropertyHandler ph = getLocalPropertyHandler(propertyName);
        try {
            return ph.getValue();
        } catch (Exception e) {
            throw new PropertyAccessException("Illegal attempt to get property '" + propertyName + "' threw exception", e);
        }
    }


    @Override
    public void setPropertyValues(PropertyValues pvs) throws BeansException {
        PropertyValue[] propertyValues = pvs.getPropertyValues();
        for (PropertyValue pv : propertyValues) {
            try{
                setPropertyValue(pv.getName(), pv.getValue());
            }catch (NotWritablePropertyException| NullValueInNestedPathException  e){
                // ignore
            }
        }
    }


    /**
     * 为最内层的属性赋值
     * @param propertyName 完整的属性名，形式类似a.b.c
     * @param value        待设置的值
     */
    @Override
    public void setPropertyValue(String propertyName, Object value) throws BeansException {
        AbstractPropertyAccessor nestedPa;
        try{
            //1. 获取属性名对应的访问器
            nestedPa = getPropertyAccessorForPropertyPath(propertyName);
        }catch (NotReadablePropertyException e){
            //属性不可读，也就不可写，因此将异常转换为写异常
            throw new NotWritablePropertyException("Bean[" + wrappedObject.getClass().getName()
                    + "]的嵌套属性[" + propertyName + "]不存在");
        }
        //2. 获取最深层次的属性并赋值
        //如果propertyName是a.b.c，那么nestPropertyName就是c
        String nestPropertyName = propertyName.substring(propertyName.lastIndexOf('.') + 1);
        nestedPa.processLocalProperty(new PropertyValue(nestPropertyName, value));
    }

    /**
     * 获取最深层次的属性的访问器（c），为此需要递归地解析出外层的嵌套属性（a和b）。如果嵌套属性为null，还需要为其构造实例
     * 假设嵌套属性的形式为a.b.c，返回结果是c的属性访问器
     *
     * @param propertyPath 嵌套属性的“路径”，递归执行的时候分别为a.b.c、b.c
     */
    private AbstractPropertyAccessor getPropertyAccessorForPropertyPath(String propertyPath) {
        //获取属性名的第一个.的下标
        int pos = propertyPath.indexOf('.');
        if(pos > -1){
            //如果propertyPath是a.b.c，则pos为1，nestedProperty为a，nestedPath为b.c
            String nestedProperty = propertyPath.substring(0, pos);
            String nestedPath = propertyPath.substring(pos + 1);

            //对于a.b.c来说，a和b都被看作一个嵌套对象，因此要为它们创建对应的属性访问器
            AbstractPropertyAccessor nestedPa = getNestedPropertyAccessor(nestedProperty);
            return nestedPa.getPropertyAccessorForPropertyPath(nestedPath);
        }
        return this;
    }


    /**
     * 获取嵌套属性的PropertyAccessor，由子类提供
     * @param nestedProperty 嵌套属性名，比如a、b
     */
    private AbstractPropertyAccessor getNestedPropertyAccessor(String nestedProperty) {
        //1. 获取 PropertyHandler
        PropertyHandler ph = getLocalPropertyHandler(nestedProperty);
        if (ph == null || !ph.isReadable()) {
            throw new NotReadablePropertyException(wrappedObject.getClass().getName(), nestedProperty);
        }

        Object value;
        try {
            value = ph.getValue();
        } catch (Exception e) {
            throw new InvalidPropertyException("尝试访问Bean[" + wrappedObject.getClass().getName()
                    + "]的属性[" + nestedProperty +"]抛出异常", e);
        }

        //2. 确保嵌套对象是存在的
        if(value == null){
            //嵌套对象可能为null，需要创建一个实例（数组、Collection、Map类型略）
            value = BeanUtils.instantiateClass(ph.getPropertyType());
            //将嵌套对象的实例赋给当前对象（将 b 赋给 a）
            processLocalProperty(new PropertyValue(nestedProperty, value));
        }
        //3. 为嵌套对象创建一个属性访问器
        return newPropertyAccessor(value);
    }


    /**
     * 对属性值进行转换，然后交由子类给属性赋值（BeanWrapper通过JavaBean的方式赋值，DirectFieldAccessor通过反射的方式赋值）
     */
    private void processLocalProperty(PropertyValue pv) {
        PropertyHandler ph = getLocalPropertyHandler(pv.getName());
        if (ph == null || !ph.isWritable()) {
            throw new NotWritablePropertyException("Bean[" + wrappedObject.getClass().getName()
                    + "]的属性[" + pv.getName() + "]setter方法不存在");
        }

        Object valueToApply = pv.getValue();
        Object oldValue = null;

        try {
            valueToApply = convertIfNecessary(valueToApply, ph.getPropertyType());
            oldValue = ph.getValue();
            ph.setValue(getWrappedInstance(), valueToApply);   //为目标对象设置属性值
        }
        //类型转换失败
        catch (TypeMismatchException ex){
            PropertyChangeEvent pce = new PropertyChangeEvent(this.wrappedObject, pv.getName(), oldValue, pv.getValue());
            throw new TypeMismatchException(pce, ph.getPropertyType(), ex);
        }
        //调用getter&setter方法时报错
        catch (InvocationTargetException ex){
            PropertyChangeEvent pce = new PropertyChangeEvent(this.wrappedObject, pv.getName(), oldValue, pv.getValue());
            throw new TypeMismatchException(pce, ph.getPropertyType(), ex.getTargetException());
        }
        catch (Exception ex){
            PropertyChangeEvent pce = new PropertyChangeEvent(this.wrappedObject, pv.getName(), oldValue, pv.getValue());
            throw new PropertyAccessException(pce, "为Bean[" + wrappedObject.getClass().getName() + "]的属性[" + pv.getName() + "]赋值失败", ex);
        }
    }


    //---------------------------------------------实现了子类BeanWrapperImpl的父接口方法(特殊)

    public final Object getWrappedInstance() {
        return this.wrappedObject;
    }


    public final Class<?> getWrappedClass() {
        return (this.wrappedObject != null ? this.wrappedObject.getClass() : null);
    }


    //---------------------------------------------抽象方法，子类实现

    /**
     * 子类实现内部类PropertyHandler，并提供创建实例的方法
     */
    protected abstract PropertyHandler getLocalPropertyHandler(String propertyName);

    /**
     * 为嵌套属性创建新的PropertyAccessor，新的属性访问器可以继承父属性PropertyAccessor的某些属性，比如ConversionService
     * @param object 目标对象始终为同一个，在所有的嵌套属性之间传递
     */
    protected abstract AbstractPropertyAccessor newPropertyAccessor(Object object);


    //---------------------------------------------内部类

    /**
     * 内部类，由子类继承，实现处理属性的具体逻辑
     */
    protected abstract static class PropertyHandler {
        private final Class<?> propertyType;
        private final boolean readable;
        private final boolean writable;

        public PropertyHandler(Class<?> propertyType, boolean readable, boolean writable) {
            this.propertyType = propertyType;
            this.readable = readable;
            this.writable = writable;
        }

        public Class<?> getPropertyType() {
            return propertyType;
        }

        public abstract Object getValue() throws Exception;

        public abstract void setValue(Object object, Object value) throws Exception;

        public boolean isReadable() {
            return this.readable;
        }

        public boolean isWritable() {
            return this.writable;
        }
    }
}
